/*!
  \file
  File containing a DimensionObject class
*/

/*
  Copyright (c) 2011, President & Fellows of Harvard College
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  * Neither the name of the Harvard College nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


#ifndef SCIGPU_HHV_DIMENSION_OBJECT_HPP
#define SCIGPU_HHV_DIMENSION_OBJECT_HPP


#include <iostream>
#include <iomanip>
#include <stdexcept>

namespace SciGPU {
  namespace HHV {
    //! A dimension object class
    /*!
      This class defines the dimensions of a hypervolume.
      It is templated on \a nDims, the number of dimensions
      of the volume, and stores the size of each dimension
      in an internal array.
      The class overrides \c operator[] to provide access to
      the dimensions themselves.
      Methods are overloaded for up to four dimensions.
      The DimensionObject::Offset method provides the 1D index
      of a given location in the hypervolume, assuming no padding
      and that the first dimension is incremented first.
      @tparam nDims       The number of dimensions
      @tparam boundsCheck Whether to fanatically check all accesses (defaults to \c true)
    */
    template<unsigned int nDims,bool boundsCheck=true> class DimensionObject {
    public:
      
      //! Default constructor
      DimensionObject( void ): dims() {};
      
      //! Mutator
      inline unsigned int& operator[]( const unsigned int iDim ) {
        if( boundsCheck ) {
          if( iDim >= nDims ) {
            throw std::out_of_range( "Incorrect Dimension" );
          }
        }
        
        return( this->dims[iDim] );
      }
      
      //! Accessor
      inline const unsigned int& operator[]( const unsigned int iDim ) const {
        if( boundsCheck ) {
          if( iDim >= nDims ) {
            throw std::out_of_range( "Incorrect Dimension" );
          }
        }
        
        return( this->dims[iDim] );
      }
      
      //! Number of dimensions stored
      inline unsigned int ndims( void ) const {
        return( nDims );
      }
      
      //! Number of hypervolume elements in the hypervolume
      inline size_t hyxels( void ) const {
        
        size_t nCells = 1;
        for( unsigned int i=0; i<nDims; i++ ) {
          nCells *= this->dims[i];
        }
        
        return( nCells );
      }
      
      
      
      //! Equality operator
      inline bool operator==( const DimensionObject& cmp ) const {
        bool match = true;
        for( unsigned int i=0; i<nDims; i++ ) {
          match = ( match && (this->dims[i] == cmp[i]) );
        }
        
        return( match );
      }
      
      
      //! Ineqality operator
      inline bool operator!=( const DimensionObject& cmp ) const {
        return( !(*this==cmp) );
      }
      
      //! Validity test (all dimensions non-zero)
      inline bool valid( void ) const {
        bool ok = true;
        for( unsigned int i=0; i<nDims; i++ ) {
          ok = ( ok && (this->dims[i] > 0 ) );
        }
        
        return( ok );
      }
      
      
      //! In bounds check (overloaded for 1D)
      inline 
      bool InBounds( const unsigned int i0 ) const {
        if( boundsCheck ) {
          if( nDims != 1 ) {
            throw std::out_of_range( __PRETTY_FUNCTION__ );
          }
        }
        
        return( (i0 < this->dims[0]) );
      }
      
      //! Bounds check (overloaded for 2D)
      inline 
      bool InBounds( const unsigned int i0,
                 const unsigned int i1 ) const {
        if( boundsCheck ) {
          if( nDims != 2 ) {
            throw std::out_of_range( __PRETTY_FUNCTION__ );
          }
        }
        
        bool inbounds = true;
        
        inbounds = ( inbounds && (i0 < this->dims[0]) );
        inbounds = ( inbounds && (i1 < this->dims[1]) );
        
        return( inbounds );
      }
      
      //! Bounds check (overloaded for 3D)
      inline 
      bool InBounds( const unsigned int i0,
                     const unsigned int i1,
                     const unsigned int i2 ) const {
        if( boundsCheck ) {
          if( nDims != 3 ) {
            throw std::out_of_range( __PRETTY_FUNCTION__ );
          }
        }
        
        bool inbounds = true;
        
        inbounds = ( inbounds && (i0 < this->dims[0]) );
        inbounds = ( inbounds && (i1 < this->dims[1]) );
        inbounds = ( inbounds && (i2 < this->dims[2]) );
        
        return( inbounds );
      }
      
      //! Bounds check (overloaded for 4D)
      inline 
      bool InBounds( const unsigned int i0,
                     const unsigned int i1,
                     const unsigned int i2,
                     const unsigned int i3 ) const {
        if( boundsCheck ) {
          if( nDims != 4 ) {
            throw std::out_of_range( __PRETTY_FUNCTION__ );
          }
        }
        
        bool inbounds = true;
        
        inbounds = ( inbounds && (i0 < this->dims[0]) );
        inbounds = ( inbounds && (i1 < this->dims[1]) );
        inbounds = ( inbounds && (i2 < this->dims[2]) );
        inbounds = ( inbounds && (i3 < this->dims[3]) );
        
        return( inbounds );
      }
      
      
      // -------------------------
      

      //! Offset operator into 1D array (overloaded for 1D)
      inline 
      size_t Offset( const unsigned int i0 ) const {
        if( !(this->InBounds( i0 )) ) {
          throw std::out_of_range( __PRETTY_FUNCTION__ );
        }
        
        return( i0 );
      }
      
      
      //! Offset operator into 1D array (overloaded for 2D)
      inline 
      size_t Offset( const unsigned int i0,
                     const unsigned int i1 ) const {
        if( !(this->InBounds( i0, i1 )) ) {
          
          throw std::out_of_range( __PRETTY_FUNCTION__ );
        }
        
        size_t offs = i1;
        offs = i0 + ( this->dims[0] * offs );
        
        return( i0 + ( i1 * this->dims[0] ) );
      }
      
      
      //! Offset operator into 1D array (overloaded for 3D)
      inline 
      size_t Offset( const unsigned int i0,
                     const unsigned int i1,
                     const unsigned int i2 ) const {
        if( !(this->InBounds( i0, i1, i2 )) ) {
          
          throw std::out_of_range( __PRETTY_FUNCTION__ );
        }
        
        size_t offs = i2;
        offs = i1 + ( this->dims[1] * offs );
        offs = i0 + ( this->dims[0] * offs );
        
        return( offs );
      }
      
      
      //! Offset operator into 1D array (overloaded for 4D)
      inline 
      size_t Offset( const unsigned int i0,
                     const unsigned int i1,
                     const unsigned int i2,
                     const unsigned int i3 ) const {
        if( !(this->InBounds( i0, i1, i2 )) ) {
          
          throw std::out_of_range( __PRETTY_FUNCTION__ );
        }
        
        size_t offs = i3;
        offs = i2 + ( offs * dims[2] );
        offs = i1 + ( offs * dims[1] );
        offs = i0 + ( offs * dims[0] );
        
        return( offs );
      }
      
    private:
      //! The actual dimension data
      unsigned int dims[nDims];
    };
    
  }
}
    
//! Stream insertion operator for DimensionObjects
template<unsigned int nDims>
std::ostream& operator<<( std::ostream& os,
                          const SciGPU::HHV::DimensionObject<nDims>& src ) {
  os << "( ";
  for( unsigned int i=0; i<nDims; i++ ) {
    os << src[i] << " ";
  }
  os << ")";
  return( os );
}




#endif
